#include <Arduino.h>
#include "STM32FreeRTOS.h"
#include "lwip/apps/httpd.h"
#include "flashFs.h"
#include "plc.h"
#include "msgpack.hpp"
#include "multipart_parser.h"
int keyindex;
struct parsedata
{
    int inContentDisposition; // flag for the right header to look for fields
    char *partname;           // field name
    char *filename;           // file name for an upload field
    FlashFile *saveto;        // file to save contents to
};
#if LWIP_HTTPD_SUPPORT_POST

extern "C"
{
#include "multipart_parser.h"
    static uint8_t http_post_uri_file_index = -1;
    static uint32_t http_post_content_len = 0;
    const char *posturls[] = {
        "/upload",
#ifdef ONLINE_DEBUG
        "/SetTraceVariablesList", "/GetTraceVariables"
#endif
    };

    /*
 * Mulitpart Parser settings
 *
 * read_on_part_data_begin: (nul
 * read_header_name: Content-Disposition: read_header_value: form-data; name="key_name"
 * read_on_headers_complete: (null)
 * read_part_data: form_value			// May be called multiple times if a file
 * read_on_part_data_end: (null)
 * read_on_body_end: (null)
 *
 */
    multipart_parser_settings callbacks;
    multipart_parser *_parser;
    const char *find_header_name(const char *header)
    {

#define HEADER_NAME_TITLE "name="
#define HEADER_NAME_TITLE_LEN 5

        if (header != NULL)
        {
            char *header_name_begin = strstr(header, HEADER_NAME_TITLE); // Find name= in Header
            char *header_name = strtok(header_name_begin, "\"");         // Find the first "
            header_name = strtok(NULL, "\"");                            // Go to the last "
#if HTTPD_DEBUG
            core_debug("POST multipart Header Key found: %s\n", header_name);
#endif
            return header_name;
        }
        return NULL;
    }
    /* Header which contains the Key with the name */
    int read_header_name(multipart_parser *parser, const char *at, size_t length)
    {
        struct parsedata *data = (struct parsedata *)multipart_parser_get_data(parser);
        data->inContentDisposition = !strncmp(at, "Content-Disposition", length);
        return 0;
    }

    int read_header_value(multipart_parser *parser, const char *at, size_t length)
    {
        struct parsedata *data = (struct parsedata *)multipart_parser_get_data(parser);
        if (data->inContentDisposition)
        {
            char hdrval[length + 1];
            strncpy(hdrval, at, length);
            if (strtok(hdrval, "; "))
            {
                char *tok;
                while ((tok = strtok(0, "; ")))
                {
                    char *rquot;
                    if (!strncmp(tok, "name=\"", 6) &&
                        ((rquot = strrchr(tok, '"')) > tok + 6))
                    {
                        *rquot = 0;
                        free(data->partname);
                        data->partname = (char *)malloc(strlen(tok + 6) + 1);
                        strcpy(data->partname, tok + 6);
                    }
                    else if (!strncmp(tok, "filename=\"", 10) &&
                             ((rquot = strrchr(tok, '"')) > tok + 10))
                    {
                        *rquot = 0;
                        free(data->filename);
                        data->filename = (char *)malloc(strlen(tok + 10) + 1);
                        strcpy(data->filename, tok + 10);
                        if (data->saveto)
                            data->saveto->close();

                        // determine local location, adapt to your needs:
                        // for production code, ADD SANITY CHECKS, the following code
                        // allows an attacker to write any location of your server
                        // with a filename containing relative paths!
                        data->saveto = new FlashFile();
                        data->saveto->open_write(data->partname);
                    }
                }
            }
        }
        return 0;
    }

    /* Value for the latest key */
    /* If this is a file, this may be called multiple times. */
    /* Wait until part_end for the complete file. */
    // char tx[] = "0123456789";
    // int inx = 0;
    int total = 0;
    int read_part_data(multipart_parser *parser, const char *at, size_t length)
    {
        int len = length;
        total += len;
        // core_debug("read_part_data: %d,total=%d\n", len, total);
        // for (int i = 0; i < length; i++)
        // {
        //     if (at[i] != tx[inx])
        //     {
        //         core_debug("error :%d", total);
        //     }
        //     inx++;
        //     total++;
        //     if (inx > 9)
        //         inx = 0;
        // }
        struct parsedata *data = (struct parsedata *)multipart_parser_get_data(parser);
        if (data->partname && data->filename )
        {
            char buf[length];
            memcpy(buf, at, length);
            FlashFile *fw = data->saveto;
            if (fw->opened())
            {
                core_debug("read_part_data: filename=%s ,len=%d\n,total=%d", fw->filename, len, total);
                fw->write(buf, len);
            }
        }
        return 0;
    }

    /* End of header which contains the key */
    int read_on_headers_complete(multipart_parser *p)
    {
        // #if HTTPD_DEBUG
        // #endif
        // FlashFile *fw = (FlashFile *)p->data;
        // fw->open_write(p->filename);
        // core_debug("read_on_headers_complete: filename=%s\n", fw->filename);
        return 0;
    }

    /* End of the entire form */
    int read_on_body_end(multipart_parser *parser)
    {
         struct parsedata *data = (struct parsedata *)multipart_parser_get_data(parser);
        // #if HTTPD_DEBUG
        core_debug("read_on_body_end: fw->close %s\n", data->filename);
        // #endif
        data->saveto->close();
        return 0;
    }

    static err_t
    http_parse_post(char *data, uint32_t length)
    {
        // core_debug("http_parse_post POST data: %d\n", length);
        char buf[length];
        memcpy(buf,data,length);
        /* Parse the data */
        multipart_parser_execute(_parser, buf, length);

        return ERR_OK;
    } /* Find boundary value in the Content-Type. */
    const char *
    find_boundary(const char *content_type)
    {

#define BOUNDARY_TITLE "boundary="
#define BOUNDARY_TITLE_LEN 9

        if (content_type != NULL)
        {
            char *boundary_begin = strstr(content_type, BOUNDARY_TITLE); // Find Boundary= in Content-Type
            char *boundary = boundary_begin + BOUNDARY_TITLE_LEN;        // Remove the Boundary=
#if HTTPD_DEBUG
            core_debug("POST multipart Boundary found: %s\n", boundary);
#endif

            return boundary;
        }
        return NULL;
    } /* Find Header Key Name in the header. */
    extern boolean POSTFILE, POSTED;
    /** Called when a POST request has been received. The application can decide
 * whether to accept it or not.
 *
 * @param connection Unique connection identifier, valid until httpd_post_end
 *        is called.
 * @param uri The HTTP header URI receiving the POST request.
 * @param http_request The raw HTTP request (the first packet, normally).
 * @param http_request_len Size of 'http_request'.
 * @param content_len Content-Length from HTTP header.
 * @param response_uri Filename of response file, to be filled when denying the
 *        request
 * @param response_uri_len Size of the 'response_uri' buffer.
 * @param post_auto_wnd Set this to 0 to let the callback code handle window
 *        updates by calling 'httpd_post_data_recved' (to throttle rx speed)
 *        default is 1 (httpd handles window updates automatically)
 * @param content_type Content-Type string.
 * @return ERR_OK: Accept the POST request, data may be passed in
 *         another err_t: Deny the POST request, send back 'bad request'.
 */
    err_t
    httpd_post_begin(void *connection,
                     const char *uri,
                     const char *http_request,
                     u16_t http_request_len,
                     int content_len,
                     char *response_uri,
                     u16_t response_uri_len,
                     u8_t *post_auto_wnd,
                     const char *content_type)
    {
        *post_auto_wnd = 0;
        int dd = 50;
        // Check the URI given with the list
        for (uint8_t i = 0; i < sizeof(posturls) / 4; i++)
        {
            if (strcmp(uri, posturls[i]) == 0)
            {

                http_post_uri_file_index = i;
                http_post_content_len = content_len;
                const char *boundary = find_boundary(content_type);
                switch (http_post_uri_file_index)
                {
                case 0:

                    while ((!POSTED) && dd--)
                    {
                        POSTFILE = 1;
                        // vTaskDelay(1);
                    }
#if HTTPD_DEBUG
                    core_debug("httpd_post_begin: Post Content: %s\n", http_request);
#endif

                    memset(&callbacks, 0, sizeof(multipart_parser_settings));

                    callbacks.on_header_field = read_header_name;
                    callbacks.on_header_value = read_header_value;
                    callbacks.on_part_data = read_part_data;
                    callbacks.on_headers_complete = read_on_headers_complete;
                    callbacks.on_body_end = read_on_body_end;

                    /*
			 * Get the boundary from the content-type
			 * Then pass it to the parser
			 */
                    if (boundary != NULL)
                    {
                        static struct parsedata data = {0};
                        memset(&data,0,sizeof(data));
                        _parser = multipart_parser_init(boundary, &callbacks);
                        multipart_parser_set_data(_parser, &data);
                    }

                    return ERR_OK;
                case 1:
                    return ERR_OK;
                case 2:

                    return ERR_OK;
                }
            }
            //returns /404.html when response_uri is empty
        }
        return ERR_VAL;
    }
#ifdef ONLINE_DEBUG

    char stxbuf[1024];

    int regVari(struct http_state *connection, struct pbuf *p)
    {

        auto oh = msgpack::unpack((const char *)p->payload, p->len);

        msgpack::object obj = oh.get();
        int l = obj.via.array.size;
        if (l > 0)
        {
            for (int i = 0; i < l; i++)
            {
                unsigned int idx;
                obj.via.array.ptr[i].via.array.ptr[0].convert(idx);
                boolean forced;
                obj.via.array.ptr[i].via.array.ptr[1].convert(forced);
                const void *force = 0;
                if (forced)
                    force = &obj.via.array.ptr[i].via.array.ptr[2].via.u64;
                RegisterDebugVariable(idx, (void *)force);
            }
        }
        else
        {
            ResetDebugVariables();
        }
        return 0;
    }
    int regVari_stream(struct http_state *connection, struct pbuf *p)
    {
        msgpack::unpacker m_pac;
        m_pac.reserve_buffer(p->len);
        while (p)
        {
            memcpy(m_pac.buffer(), p->payload, p->len);

            m_pac.buffer_consumed(p->len);

            msgpack::object_handle oh;
            while (m_pac.next(oh))
            {
                msgpack::object obj = oh.get();
                int s = obj.via.array.size;
                for (int i = 0; i < s; i++)
                {
                    unsigned int idx;
                    obj.via.array.ptr[i].via.array.ptr[0].convert(idx);
                    boolean forced;
                    obj.via.array.ptr[i].via.array.ptr[1].convert(forced);
                    const void *force = 0;
                    if (forced)
                        force = &obj.via.array.ptr[i].via.array.ptr[2].via.u64;
                    RegisterDebugVariable(idx, (void *)force);
                }
            }
            p = p->next;
        }
    }
    int readVari()
    {
        unsigned long tick = 0;
        unsigned long size = 0;
        char *buffer = 0;
        msgpack::sbuffer sbuf(1024);
        if (plc_status == Started)
        {
            if (GetDebugData(&tick, &size, (void **)&buffer) == 0)
            {
                msgpack::type::tuple<std::string, int, msgpack::type::raw_ref> src(plc_str[plc_status], tick, msgpack::type::raw_ref(buffer, size));
                msgpack::pack(sbuf, src);

                memcpy(stxbuf, sbuf.data(), sbuf.size());
                endDebugData();
            }
        }
        return sbuf.size();
    }
    int readVari_stream(struct http_state *connection, struct pbuf *p)
    {
        unsigned long tick = 0;
        unsigned long size = 0;
        char *buffer = 0;
        msgpack::sbuffer sbuf(1024);
        if (plc_status == Started)
        {
            if (GetDebugData(&tick, &size, (void **)&buffer) == 0)
            {
                msgpack::type::tuple<std::string, int, msgpack::type::raw_ref> src(plc_str[plc_status], tick, msgpack::type::raw_ref(buffer, size));
                msgpack::pack(sbuf, src);

                memcpy(stxbuf, sbuf.data(), sbuf.size());
                endDebugData();
            }
        }
        return sbuf.size();
    }
#endif
    /** Called for each pbuf of data that has been received for a POST.
 * ATTENTION: The application is responsible for freeing the pbufs passed in!
 *
 * @param connection Unique connection identifier.
 * @param p Received data.
 * @return ERR_OK: Data accepted.
 *         another err_t: Data denied, http_post_get_response_uri will be called.
 */
    err_t
    httpd_post_receive_data(void *connection, struct pbuf *p)
    {

        char *data;
        err_t ret_val = ERR_ARG;
        struct http_state *hs = (struct http_state *)connection;
        struct pbuf *q = p;
        while (p != NULL)
        {
            switch (http_post_uri_file_index)
            {
            case 0:
                data = (char *)p->payload;
                ret_val = http_parse_post(data, p->len);

                break;
#ifdef ONLINE_DEBUG
            case 1:
            {

                if (plc_status == Started)
                {
                    regVari(hs, p);
                    hs->file = "/ok.html";
                    ret_val = ERR_OK;
                    break;
                }
                // core_debug(error.c_str());
                hs->file = "/error.html";
                ret_val = ERR_OK;
            }
            break;
            case 2:
            {

                hs->left = readVari();
                hs->file = stxbuf;
                ret_val = ERR_OK;
            }
            break;
#endif
            default:
                LWIP_ASSERT("CASE", 0);
            }
            httpd_post_data_recved(hs, p->len);
            p = p->next;
        }
        pbuf_free(q);
        return ret_val;
    }

    /** Called when all data is received or when the connection is closed.
 * The application must return the filename/URI of a file to send in response
 * to this POST request. If the response_uri buffer is untouched, a 404
 * response is returned.
 *
 * @param connection Unique connection identifier.
 * @param response_uri Filename of response file on success
 * @param response_uri_len Size of the 'response_uri' buffer.
 */
    int
    httpd_post_finished(void *connection,
                        char *response_uri,
                        u16_t response_uri_len)
    {

        struct http_state *hs = (struct http_state *)connection;
        if (hs != NULL)
        {

            switch (http_post_uri_file_index)
            {
            case 0:
                strcpy(response_uri, "/index.html");

                /* End the parser */
                // free(_parser->data);
                multipart_parser_free(_parser);
                POSTFILE = 0;
                break;
            case 1:
                strcpy(response_uri, hs->file);
                break;
            case 2:
                strcpy(response_uri, "/o.cls"); //用于指定数据格式
                return 2;
            }
        }
        return 0;
    }
}
#endif
